/*
  The printf function
*/

#include <types.h>
#include <printf.h>
#include <stdio.h>
#include <out_buffer.h>

#define BUF_SZ 15

/*
Someday look at this:

http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/ucblib/libucb/port/stdio/doprnt.c
*/

/* Private functions Prototypes */

int baseconv(unsigned long num,unsigned long base,char *buf,int size);


/* Public functions */

int printf(const char *fmt, ...)
{
  va_list ap;
  int d,i,j;
  char c, *p, *s;
  char ach;
  int waitarg,escaped,ct;
  int waitextra,pad,size;
  char buf[BUF_SZ];

  // clear buffer
  ob_clear();
  
  waitarg=escaped=ct=0;

  va_start(ap, fmt);
  while (*fmt)
    {
      ach=*fmt++;     /* retrieve actual character */
      
      if (waitarg)    /* if we are waiting for an argument */
	{	
	  if (waitextra && ach>='0' && ach<='9')
	    {
	      waitextra=0;

	      if (ach=='0')		
		pad=1;

	      ach=*fmt++;     /* retrieve new character */
	      
	      /* check whether the string has more characters */
	      if (!ach)
		{
		  // we should handle this error better
		  fmt--;
		  continue;
		}
      
	      // this is lousy for now (allows size to be 1 char only... fix it later)
	      if (ach>='1' && ach<='9')
		size=(int)(ach-'0');

	      continue;  /* read next char */
	    }
 
	  waitextra=0; 
	  waitarg=0;  /* turn flag off */
	  
	  switch(ach)
	    {
	    case 's':           /* string */
	      s = va_arg(ap,char *);
	      while (*s)
		{
		  ob_enqueue(*s++);
		  ct++;
		}
	      break;
	      
	    case 'd':           /* int -> decimal print */
	      d = va_arg(ap,dword_t);
	      for (i=baseconv(d,10,buf,BUF_SZ);i>0;i--)		
		{
		  ob_enqueue(buf[i-1]);		  	      
		  ct++;
		}
	      break;
	      
	    case 'x':           /* int -> hexadecimal print */
	      d = va_arg(ap,dword_t);
	      i=baseconv(d,16,buf,size);  //
	      if (i>0 && i<size)
		{
		  // this would automatically display '0x'
		  //putchar('0');
		  //putchar('x');
		  //ct+=2;

		  if (pad)
		    {
		      j=size-i;
		      while (j--)
			{
			  ob_enqueue('0');
			  ct++;
			}
		    }

		}
	      for (;i>0;i--)		
		{
		  ob_enqueue(buf[i-1]);
		  ct++;
		}
	      break;
	      
	    case 'c':           /* char */
	      /* need a cast here since va_arg only
		 takes fully promoted types */
	      c = (char) va_arg(ap,int);
	      ob_enqueue(c);
	      ct++;
	      break;	      
	      
	    default:
	      ob_enqueue('%');  //no format specifier found
	      ob_enqueue(' ');
	      ct+=2;	      
	    }	  	  
	}
      else		
	{
	  //we're not expecting an argument
	  switch (ach)
	    {
	    case '%':
	      waitarg=1;
	      waitextra=1;
	      escaped=0;
	      pad=0;
	      size=8; // default
	      break;
	      
	    default:
	      ob_enqueue(ach);
	      ct++;
	    }	  
	}
    }

  // send buffer
  ob_submit();

  va_end(ap);  
  return ct;
}



/* Private functions */


// Convert a number to a string in any base (radix) and save it to a buffer in reversed order
int baseconv(unsigned long num,unsigned long base,char *buf,int size)
  {
    int ct=0;
    unsigned long rem;
    unsigned long num2;

    
    // Truncate the number to the specified size
    // (mask the sign extension)
    // a bit dirty... but it works
    if (size<8)
      {
	num2=0xFFFFFFFF;
	num2>>=size*4;
	num2<<=size*4;
	num2=~num2;

	num&=num2;
      }

    if (num==0)
      {
	buf[0]='0';
	return 1;	
      }

    for (ct=0;num!=0 && ct<BUF_SZ;ct++)
      {
	if (base)
	  {
	    rem=num%base;
	    num/=base;
	  }
	else
	  {
	    printf("\nLIBC(baseconv): base is zero!\n");
	    return ct;
	  }
	
	buf[ct]= rem>=10 ? rem-10+'A' : rem+'0';
      }
   
    return ct;
  }

